﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Security.Principal;
using System.Web.Mvc;
using System.Web.Mvc.Ajax;
using System.Web.Security;
using System.Web.UI;
using Ashlen.Portal.Core;
using Ashlen.Portal.Core.DataInterfaces;
using Ashlen.Portal.Data.SqlServer;
using System.Web;
using System.Configuration;

namespace Ashlen.Portal.Controllers
{

    [HandleError]
    [OutputCache(Location = OutputCacheLocation.None)]
    public class AccountController : Controller
    {
        public class SelectObject
        {
            private string _name;

            public string Name
            {
                get { return _name; }
                set { _name = value; }
            }

            private string _value;

            public string Value
            {
                get { return _value; }
                set { _value = value; }
            }
        }

        private readonly ISecurityDao _securityDao;
        private readonly ILookupDao _lookUpDao;
        private readonly IUtilityDao _utility;
        private HttpContext _httpContext;

        public AccountController()
        {
            FormsAuth = new FormsAuthenticationWrapper();
            Provider = Membership.Provider;
            _securityDao = new SecurityDao();
            _lookUpDao = new LookupDao();
            _utility = new UtilityDao();
        }

        public AccountController(ISecurityDao securityDao, ILookupDao lookupDao, IUtilityDao utilityDao, HttpContext context)
        {
            _httpContext = context;
            FormsAuth = new FormsAuthenticationWrapper();
            Provider = Membership.Provider;
            _securityDao = securityDao;
            _lookUpDao = lookupDao;
            _utility = utilityDao;
        }

        public HttpContext Context
        {
            get
            {
                if (_httpContext == null)
                {
                    _httpContext = System.Web.HttpContext.Current;
                }
                return _httpContext;
            }
        }

        public AccountController(string connectionString)
        {
            FormsAuth = new FormsAuthenticationWrapper();
            Provider = Membership.Provider;
            _lookUpDao = new LookupDao(connectionString);
            _securityDao = new SecurityDao(connectionString);
            _utility = new UtilityDao(connectionString);
        }

        public AccountController(IFormsAuthentication formsAuth, MembershipProvider provider, HttpContext context)
        {
            _httpContext = context;
            FormsAuth = formsAuth ?? new FormsAuthenticationWrapper();
            Provider = provider ?? Membership.Provider;
        }

        public IFormsAuthentication FormsAuth
        {
            get;
            private set;
        }

        public MembershipProvider Provider
        {
            get;
            private set;
        }

        [Authorize]
        public ActionResult MySite()
        {
            return View("EditProfile");
        }

        [Authorize]
        public ActionResult ChangePassword(string currentPassword, string newPassword, string confirmPassword)
        {
            ViewData["Title"] = Ashlen.Portal.Data.SqlServer.UtilityDao.Instance.GetConfig(Ashlen.Portal.Data.SqlServer.Constants.APPLICATION, "ApplicationName") + " - Change Password";
            ViewData["PasswordLength"] = Provider.MinRequiredPasswordLength;

            // Non-POST requests should just display the ChangePassword form 
            if (Request.HttpMethod != "POST")
            {
                return View();
            }

            // Basic parameter validation
            List<string> errors = new List<string>();

            if (String.IsNullOrEmpty(currentPassword))
            {
                errors.Add("You must specify a current password.");
            }
            if (newPassword == null || newPassword.Length < Provider.MinRequiredPasswordLength)
            {
                errors.Add(String.Format(CultureInfo.InvariantCulture,
                         "You must specify a new password of {0} or more characters.",
                         Provider.MinRequiredPasswordLength));
            }
            if (!String.Equals(newPassword, confirmPassword, StringComparison.Ordinal))
            {
                errors.Add("The new password and confirmation password do not match.");
            }

            if (errors.Count == 0)
            {

                // Attempt to change password
                MembershipUser currentUser = Provider.GetUser(User.Identity.Name, true /* userIsOnline */);
                bool changeSuccessful = false;
                try
                {
                    changeSuccessful = currentUser.ChangePassword(currentPassword, newPassword);
                }
                catch
                {
                    // An exception is thrown if the new password does not meet the provider's requirements
                }

                if (changeSuccessful)
                {
                    return RedirectToAction("ChangePasswordSuccess");
                }
                else
                {
                    errors.Add("The current password is incorrect or the new password is invalid.");
                }
            }

            // If we got this far, something failed, redisplay form
            ViewData["errors"] = errors;
            return View();
        }

        public ActionResult ChangePasswordSuccess()
        {

            ViewData["Title"] = Ashlen.Portal.Data.SqlServer.UtilityDao.Instance.GetConfig(Ashlen.Portal.Data.SqlServer.Constants.APPLICATION, "ApplicationName") + " - Change Password";

            return View();
        }

        public ActionResult Activate(string id, string id2)
        {
            ViewData["Title"] = Ashlen.Portal.Data.SqlServer.UtilityDao.Instance.GetConfig(Ashlen.Portal.Data.SqlServer.Constants.APPLICATION, "ApplicationName") + " - Account Activation";
            MembershipUser user = Membership.GetUser(id);
            if (user == null)
            {
                ViewData["Message"] = string.Format("User {0} not found.", id);
                return View();
            }
            else
            {
                if (user.IsApproved == false)
                {
                    aspnet_UserPersonalInfo info = _securityDao.GetPersonalInfo(new Guid(user.ProviderUserKey.ToString())) as aspnet_UserPersonalInfo;
                    if (info.Comment != id2)
                    {
                        ViewData["Message"] = "Activation url is invalid.";
                        return View();
                    }
                    user.IsApproved = true;
                    Membership.UpdateUser(user);
                }
                else
                {
                    ViewData["Message"] = string.Format("User {0} has already been activated. <br/>Please click <a href=\"/Account/Login\">here</a> to login.",
                        id);
                    return View();
                }
            }
            ViewData["Message"] = "Your account has been activated. <br/>Please click <a href=\"/Account/Login\">here</a> to login.";
            return View();
        }

        public ActionResult RegisterSuccess()
        {

            ViewData["Title"] = Ashlen.Portal.Data.SqlServer.UtilityDao.Instance.GetConfig(Ashlen.Portal.Data.SqlServer.Constants.APPLICATION, "ApplicationName") + " - Registered";
            return View();
        }

        public ActionResult Login(string username, string password, bool? rememberMe, string returnUrl)
        {
            ViewData["Title"] = Ashlen.Portal.Data.SqlServer.UtilityDao.Instance.GetConfig(Ashlen.Portal.Data.SqlServer.Constants.APPLICATION, "ApplicationName") + " - Login";

            // Non-POST requests should just display the Login form 
            if (Request.HttpMethod != "POST")
            {
                return View();
            }

            // Basic parameter validation
            var errors = new List<string>();

            if (String.IsNullOrEmpty(username))
            {
                errors.Add("You must specify a username.");
            }

            if (errors.Count == 0)
            {

                // Attempt to login
                bool loginSuccessful = Provider.ValidateUser(username, password);

                if (loginSuccessful)
                {
                    FormsAuthentication.Initialize();
                    FormsAuthentication.Authenticate(username, password);
                    FormsAuth.SetAuthCookie(username, rememberMe ?? false);
                    _utility.Log("PORTAL", LogSeverity.INFORMATION, "General", string.Empty, string.Format("{0} logged in at {1}", username, DateTime.Now));

                    if (!String.IsNullOrEmpty(returnUrl))
                    {
                        return Redirect(returnUrl);
                    }
                    else
                    {
                        return RedirectToAction("Index", "Home");
                    }
                }
                else
                {
                    errors.Add("The username or password provided is incorrect or account has not been activated.");
                }
            }

            // If we got this far, something failed, redisplay form
            ViewData["errors"] = errors;
            ViewData["username"] = username;
            return View();
        }

        public ActionResult Logout()
        {
            try
            {
                _utility.Log(Constants.APPLICATION, LogSeverity.INFORMATION, "General",
                    string.Empty, string.Format("{0} logged out at {1}", HttpContext.User.Identity.Name, DateTime.Now));
            }
            catch { }
            FormsAuth.SignOut();
            Response.Cache.SetCacheability(HttpCacheability.NoCache);
            Response.Cache.SetNoStore(); 

            return RedirectToAction("Index", "Home");
        }

        protected override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            if (filterContext.HttpContext.User.Identity is WindowsIdentity)
            {
                throw new InvalidOperationException("Windows authentication is not supported.");
            }
        }

        [Authorize]
        public ActionResult Update(string email, string password, string confirmPassword,
            string firstName, string lastName, string nickName,
            DateTime? dateOfBirth, string sex, string mobile, string phone, string country,
            string state, int? postcode, bool? acceptCommunityEmail, bool? acceptAdvertisingEmail)
        {
            bool showDataOnly = false;
            string username = (Context != null ? Context.User.Identity.Name : string.Empty);
            ViewData["Title"] = Ashlen.Portal.Data.SqlServer.UtilityDao.Instance.GetConfig(Ashlen.Portal.Data.SqlServer.Constants.APPLICATION, "ApplicationName") + " - Update Details";
            ViewData["PasswordLength"] = Provider.MinRequiredPasswordLength;

            // Non-POST requests should just display the Register form 
            if (Request.HttpMethod != "POST")
            {
                /* retrieve all user details and display on page */
                showDataOnly = true;
            }

            // Basic parameter validation
            var errors = new List<string>();

            if (showDataOnly == false)
            {
                if (String.IsNullOrEmpty(username))
                {
                    errors.Add("You must specify a username.");
                }
                if (String.IsNullOrEmpty(email))
                {
                    errors.Add("You must specify an email address.");
                }
                if (!string.IsNullOrEmpty(password))
                {
                    if (password.Length < Provider.MinRequiredPasswordLength)
                    {
                        errors.Add(String.Format(CultureInfo.InvariantCulture,
                                                 "You must specify a password of {0} or more characters.",
                                                 Provider.MinRequiredPasswordLength));
                    }

                    if (!String.Equals(password, confirmPassword, StringComparison.Ordinal))
                    {
                        errors.Add("The password and confirmation do not match.");
                    }
                }
                if (String.IsNullOrEmpty(firstName))
                {
                    errors.Add("You must specify a first name.");
                }
                if (String.IsNullOrEmpty(lastName))
                {
                    errors.Add("You must specify a last name.");
                }
                if (String.IsNullOrEmpty(sex))
                {
                    errors.Add("You must specify a gender.");
                }
                if (String.IsNullOrEmpty(country))
                {
                    errors.Add("You must specify a country.");
                }
                if (String.IsNullOrEmpty(state))
                {
                    errors.Add("You must specify a state.");
                }
            }
            if (errors.Count == 0)
            {

                // Attempt to update the user
                MembershipCreateStatus createStatus;
                MembershipUser user = Provider.GetUser(username, true);
                var info = _securityDao.GetPersonalInfo((user != null ? new Guid(user.ProviderUserKey.ToString()) : default(Guid))) as aspnet_UserPersonalInfo;
                var pref = _securityDao.GetEmailPref((user != null ? new Guid(user.ProviderUserKey.ToString()) : default(Guid))) as aspnet_UserEmailPreference;

                if (showDataOnly)
                {
                    email = (user != null ? user.Email : string.Empty);
                    firstName = info.Firstname;
                    lastName = info.Lastname;
                    nickName = info.Nickname;
                    dateOfBirth = info.DateOfBirth;
                    sex = info.Sex;
                    mobile = info.Mobile;
                    phone = info.Phone;
                    country = info.Country;
                    state = info.State;
                    postcode = info.Postcode;
                    acceptCommunityEmail = pref.AcceptCommunityEmail;
                    acceptAdvertisingEmail = pref.AcceptAdvertisingEmail;
                }
                else
                {
                    /* Only change the password if another has been supplied */
                    if (!string.IsNullOrEmpty(password))
                    {
                        if (password != user.GetPassword())
                            user.ChangePassword(user.GetPassword(), password);
                    }

                    user.Email = email;
                    Provider.UpdateUser(user);
                    if (user != null)
                    {
                        /* Create extra user data */
                        _securityDao.CreatePersonalInfo(new Guid(user.ProviderUserKey.ToString()), firstName, lastName,
                                                        nickName,
                                                        dateOfBirth, sex, mobile, phone,
                                                        country, state, postcode, System.Guid.NewGuid().ToString(),
                                                        acceptCommunityEmail.Value, acceptAdvertisingEmail.Value);
                        FormsAuth.SetAuthCookie(username, false /* createPersistentCookie */);
                    }
                }
            }
            // Redisplay form with update status
            ViewData["errors"] = errors;
            ViewData["username"] = username;
            ViewData["email"] = email;
            ViewData["firstName"] = firstName;
            ViewData["lastName"] = lastName;
            ViewData["nickName"] = nickName;
            ViewData["dateOfBirth"] = (dateOfBirth.HasValue ? dateOfBirth.Value.ToString("dd-MMM-yyyy") : string.Empty);
            //            ViewData["sex"] = sex;
            ViewData["sex"] = new SelectList(GetGenders(), "Name", "Value", sex);
            ViewData["mobile"] = mobile;
            ViewData["phone"] = phone;
            ViewData["selectedState"] = state;
            ViewData["postcode"] = postcode;
            //ViewData["country"] = new SelectList(_lookUpDao.GetCountries(), "ID", "Name", country);
            ViewData["acceptCommunityEmail"] = (acceptCommunityEmail == null) || acceptCommunityEmail.Value;
            ViewData["acceptAdvertisingEmail"] = (acceptAdvertisingEmail == null) || acceptAdvertisingEmail.Value;
            if (Request.IsAjaxRequest())
            {
                if (errors.Count > 0)
                {
                    return Json(errors);
                }
                else
                {
                    return Json(string.Format("Welcome {0} {1}!", firstName, lastName));
                }
            }
            else
            {
                return View("Change");
            }
        }

        public string UserName()
        {
            string username = (Context != null ? Context.User.Identity.Name : string.Empty);
            MembershipUser user = Provider.GetUser(username, true);
            var info =
                _securityDao.GetPersonalInfo((user != null ? new Guid(user.ProviderUserKey.ToString()) : default(Guid))) as aspnet_UserPersonalInfo;
            return string.Format("{0} {1}", info.Firstname, info.Lastname);
        }

        public string UserUniqueId()
        {
            string username = (Context != null ? Context.User.Identity.Name : string.Empty);
            MembershipUser user = Provider.GetUser(username, true);
            return user.ProviderUserKey.ToString();
        }

        public ActionResult GetStatesForCountry(string id)
        {
            return Json(_lookUpDao.GetStates(id));
        }

        protected List<SelectObject> GetGenders()
        {
            List<SelectObject> genders = new List<SelectObject>();
            genders.Add(new SelectObject() { Name = "M", Value = "Male" });
            genders.Add(new SelectObject() { Name = "F", Value = "Female" });

            return genders;
        }

        public ActionResult Register()
        {
            ViewData["Title"] = Ashlen.Portal.Data.SqlServer.UtilityDao.Instance.GetConfig(Ashlen.Portal.Data.SqlServer.Constants.APPLICATION, "ApplicationName") + " - Register";
            // sign out the user, if logged in
            if (this.Request.IsAuthenticated)
            {
                FormsAuth.SignOut();
                return RedirectToAction("Register", "Account");
            }
            ViewData["Title"] = Ashlen.Portal.Data.SqlServer.UtilityDao.Instance.GetConfig(Ashlen.Portal.Data.SqlServer.Constants.APPLICATION, "ApplicationName") + " - Register";
            ViewData["PasswordLength"] = Provider.MinRequiredPasswordLength;
            return View("Register");
        }


        public ActionResult Register2(string username, string email, string password, string confirmPassword, string firstName, string lastName,
           string nickName, DateTime? dateOfBirth, string sex, string mobile, string phone, string country, string state, int? postcode,
           bool? acceptCommunityEmail, bool? acceptAdvertisingEmail)
        {

            ViewData["Title"] = Ashlen.Portal.Data.SqlServer.UtilityDao.Instance.GetConfig(Ashlen.Portal.Data.SqlServer.Constants.APPLICATION, "ApplicationName") + " - Register";
            ViewData["PasswordLength"] = Provider.MinRequiredPasswordLength;
            bool getOnly = false;
            // Non-POST requests should just display the Register form 
            if (Request.HttpMethod != "POST")
            {
                getOnly = true;
                //return View();
            }

            // Basic parameter validation
            var errors = new List<string>();

            if (!getOnly)
            {
                if (String.IsNullOrEmpty(username))
                {
                    errors.Add("You must specify a username.");
                }
                if (String.IsNullOrEmpty(email))
                {
                    errors.Add("You must specify an email address.");
                }
                if (password == null || password.Length < Provider.MinRequiredPasswordLength)
                {
                    errors.Add(String.Format(CultureInfo.InvariantCulture,
                                             "You must specify a password of {0} or more characters.",
                                             Provider.MinRequiredPasswordLength));
                }
                if (!String.Equals(password, confirmPassword, StringComparison.Ordinal))
                {
                    errors.Add("The password and confirmation do not match.");
                }
                if (String.IsNullOrEmpty(firstName))
                {
                    errors.Add("You must specify a first name.");
                }
                if (String.IsNullOrEmpty(lastName))
                {
                    errors.Add("You must specify a last name.");
                }
                if (String.IsNullOrEmpty(sex))
                {
                    errors.Add("You must specify a gender.");
                }
                if (String.IsNullOrEmpty(country))
                {
                    errors.Add("You must specify a country.");
                }
                if (String.IsNullOrEmpty(state))
                {
                    errors.Add("You must specify a state.");
                }

                if (errors.Count == 0)
                {

                    // Attempt to register the user
                    MembershipCreateStatus createStatus;
                    MembershipUser newUser = Provider.CreateUser(username, password, email, null, null, false, null,
                                                                 out createStatus);

                    if (newUser != null)
                    {
                        string activationGuid = System.Guid.NewGuid().ToString();
                        /* Create extra user data */
                        _securityDao.CreatePersonalInfo(new Guid(newUser.ProviderUserKey.ToString()), firstName,
                                                        lastName, nickName,
                                                        dateOfBirth, sex, mobile, phone,
                                                        country, state, postcode, activationGuid, acceptCommunityEmail.Value,
                                                        acceptAdvertisingEmail.Value);

                        // send confirmation email
                        _utility.SendEmail(Constants.APPLICATION, true, _utility.GetConfig(Constants.APPLICATION, "NoReplyEmail"),
                            email, string.Empty, string.Empty,
                            string.Format("{0} Account Confirmation", _utility.GetConfig(Constants.APPLICATION, "ApplicationUrl")),
                            CreateEmail(firstName, username, activationGuid));

                        //FormsAuth.SetAuthCookie(username, false /* createPersistentCookie */);
                        //return RedirectToAction("Index", "Home");
                        return RedirectToAction("RegisterSuccess");
                    }
                    else
                    {
                        errors.Add(ErrorCodeToString(createStatus));
                    }
                }
            }

            // If we got this far, something failed, redisplay form
            ViewData["errors"] = errors;
            ViewData["username"] = username;
            ViewData["email"] = email;
            ViewData["firstName"] = firstName;
            ViewData["lastName"] = lastName;
            ViewData["nickName"] = nickName;
            ViewData["dateOfBirth"] = dateOfBirth;
            ViewData["sex"] = sex;
            ViewData["mobile"] = mobile;
            ViewData["phone"] = phone;
            //ViewData["country"] = new SelectList(_lookUpDao.GetCountries(), "ID", "Name", country);
            ViewData["selectedState"] = state;
            ViewData["postcode"] = postcode;
            ViewData["acceptCommunityEmail"] = (acceptCommunityEmail == null) || acceptCommunityEmail.Value;
            ViewData["acceptAdvertisingEmail"] = (acceptAdvertisingEmail == null) || acceptAdvertisingEmail.Value;
            return View("Register");
        }

        private string CreateEmail(string firstName, string username, string activationGuid)
        {

            var emailBody = string.Format(_utility.GetFromEmailTemplate("WelcomeEmail"), Environment.NewLine, firstName, username, activationGuid);
            return emailBody;
        }

        public static string ErrorCodeToString(MembershipCreateStatus createStatus)
        {
            // See http://msdn.microsoft.com/en-us/library/system.web.security.membershipcreatestatus.aspx for
            // a full list of status codes.
            switch (createStatus)
            {
                case MembershipCreateStatus.DuplicateUserName:
                    return "Username already exists. Please enter a different user name.";

                case MembershipCreateStatus.DuplicateEmail:
                    return "A username for that e-mail address already exists. Please enter a different e-mail address.";

                case MembershipCreateStatus.InvalidPassword:
                    return "The password provided is invalid. Please enter a valid password value.";

                case MembershipCreateStatus.InvalidEmail:
                    return "The e-mail address provided is invalid. Please check the value and try again.";

                case MembershipCreateStatus.InvalidAnswer:
                    return "The password retrieval answer provided is invalid. Please check the value and try again.";

                case MembershipCreateStatus.InvalidQuestion:
                    return "The password retrieval question provided is invalid. Please check the value and try again.";

                case MembershipCreateStatus.InvalidUserName:
                    return "The user name provided is invalid. Please check the value and try again.";

                case MembershipCreateStatus.ProviderError:
                    return "The authentication provider returned an error. Please verify your entry and try again. If the problem persists, please contact your system administrator.";

                case MembershipCreateStatus.UserRejected:
                    return "The user creation request has been canceled. Please verify your entry and try again. If the problem persists, please contact your system administrator.";

                default:
                    return "An unknown error occurred. Please verify your entry and try again. If the problem persists, please contact your system administrator.";
            }
        }

    }

    // The FormsAuthentication type is sealed and contains static members, so it is difficult to
    // unit test code that calls its members. The interface and helper class below demonstrate
    // how to create an abstract wrapper around such a type in order to make the AccountController
    // code unit testable.

    public interface IFormsAuthentication
    {
        void SetAuthCookie(string userName, bool createPersistentCookie);
        void SignOut();
    }

    public class FormsAuthenticationWrapper : IFormsAuthentication
    {
        public void SetAuthCookie(string userName, bool createPersistentCookie)
        {
            FormsAuthentication.SetAuthCookie(userName, createPersistentCookie);
        }
        public void SignOut()
        {
            FormsAuthentication.SignOut();
        }
    }
}
